#include <cstdlib>
#include <ctime>

#include <Geex/graphics/geexapp.h>
#include <Geex/basics/file_system.h>
#include <glut_viewer/tweak_bar.h>
#include <glut_viewer/glut_viewer.h>
#include <GL/GLU.h>
#include <Geex/third_party/misc/gl2ps.h>

#include <CGAL/Timer.h>

#include <boost/thread.hpp>

#include "packing_aggregator.h"
#include "../common/error_info.h"

extern Config_parameters::Opti_alg Config_parameters::opti_algo;
extern double Config_parameters::gravity_scale;

namespace Field_packing {
		
	using namespace Geex;

	//void TW_CALL tw_reset(void *clientData);
	//void TW_CALL tw_feature_set_callback(const void*, void *);
	//void TW_CALL tw_feature_get_callback(void*, void*);
	//void TW_CALL tw_scale_get_callback(void*, void*);
	//void TW_CALL tw_scale_set_callback(const void*, void*);
	void TW_CALL tw_pack(void*);
	void TW_CALL tw_move(void*);
	void TW_CALL tw_get_energy(void*);
	void TW_CALL tw_plot(void*);
	void TW_CALL tw_save_png(void*);
	//void TW_CALL tw_fluctuate(void*);
	void TW_CALL tw_lower_container(void*);
	//void TW_CALL tw_del_tri(void*);
	void TW_CALL tw_det_holes(void*);
	void TW_CALL tw_fill_holes(void*);
	void TW_CALL tw_retrieve_vacancy(void*);
	void TW_CALL tw_permute(void*);
	void TW_CALL tw_transfer_state(void*);
	void TW_CALL tw_retriangulate(void*);
	void TW_CALL tw_update_triangulation(void*);
	void TW_CALL tw_sa(void*);
	void TW_CALL tw_multistart(void*);
	void TW_CALL tw_print_pdf(void*);

    class Field_packing_application : public GeexApp 
	{
    public:
        Field_packing_application(int argc, char** argv) : GeexApp(argc, argv) 
		{ 
            hdr_ = false ;
  
           // get_arg("nb_iter", nb_iter_) ;

			polygon_filename = get_file_arg("txt");
			if (polygon_filename.empty())
				polygon_filename = get_file_arg("poly");

			dx = dy = 0.0;
			std::cout<<"Used "<<polygon_filename<<" as input.\n";
		}

        Packing_aggregator* packing_context() { return static_cast<Packing_aggregator*>(scene()) ; }

        void init_scene() 
		{
            scene_ = new Packing_aggregator(polygon_filename, debug_filename, 0.7);
        }

		//GLboolean mouse(float x, float y, int button, enum GlutViewerEvent event) 
		//{
		//	GLdouble pos[3];
		//	GLboolean hb;
		//	glut_viewer_get_picked_point(pos, &hb);
		//	if (event == GLUT_VIEWER_UP)
		//	{
		//		packing_context()->hand_select(pos[0], pos[1]);
		//	}
		//	return GL_TRUE;
		//}

		void pack()
		{
			// sychronized one
			//boost::thread comp_thread(&Packing_aggregator::pack, packing_context(), 1);
			// non synchronized one
			//packing_context()->pack(1);
			//glut_viewer_redraw();
			// kd-tree based distance query
			packing_context()->cen_pack();
			glut_viewer_redraw();
		}
		//void move()
		//{
		//	packing_context()->move(dx, dy);
		//	glut_viewer_redraw();
		//}
		void get_energy()
		{
			double e = packing_context()->current_energy();
			std::cout<<"Current system energy = "<<e<<std::endl;
		}
		//void gen_delaunay()
		//{
		//	packing_context()->gen_delaunay();
		//	glut_viewer_redraw();
		//}
		void detect_holes()
		{
			packing_context()->detect_bnd_holes();
			glut_viewer_redraw();
		}
		void fill_holes()
		{
			packing_context()->fill_holes();
			glut_viewer_redraw();
		}
		void plot()
		{
			packing_context()->plot();
		}
		void save_png()
		{
			copy_image_to_png("test.png");
		}
		void lower_container()
		{
			packing_context()->lower_container();
			glut_viewer_redraw();
		}
		void retrieve_vacancy()
		{
			packing_context()->extract_vacant_neighbor();
		}
		void permute()
		{
			packing_context()->permute();
			glut_viewer_redraw();
		}
		void transfer_state()
		{
			packing_context()->move_to_neighbor_state();
			glut_viewer_redraw();
		}
		void retriangulate()
		{
			packing_context()->gen_delaunay();
			glut_viewer_redraw();
		}
		void update_triangulation()
		{
			packing_context()->update_triangulation();
			glut_viewer_redraw();
		}
		void run_sa()
		{
			packing_context()->run_sa(&glut_viewer_redraw);
			glut_viewer_redraw();
		}
		void multistart()
		{
			packing_context()->multistart_search(&glut_viewer_redraw);
			glut_viewer_redraw();
		}
        void init_gui() 
		{
            GeexApp::init_gui() ;
			
            TwBar* graphics_bar = TwNewBar("Graphics");
			TwDefine("Graphics position='16 10' size='200 190' alpha=200"); 
			TwAddVarRW(graphics_bar, "Polygons", TW_TYPE_BOOL8, &packing_context()->toggle_polygons(), "");
			TwAddVarRW(graphics_bar, "Field", TW_TYPE_BOOL8, &packing_context()->toggle_distance_field(), "");
			TwAddVarRW(graphics_bar, "Highlight No.", TW_TYPE_INT32, &packing_context()->set_highlight_no(), "");
			TwAddVarRW(graphics_bar, "Holes", TW_TYPE_BOOL8, &packing_context()->toggle_holes(), "");
			TwAddVarRW(graphics_bar, "Triangulation", TW_TYPE_BOOL8, &packing_context()->toggle_delaunay(), "");
			TwAddVarRW(graphics_bar, "CAT", TW_TYPE_BOOL8, &packing_context()->toggle_CAT(), "");
			TwAddVarRW(graphics_bar, "Vacant", TW_TYPE_BOOL8, &packing_context()->toggle_vacant_space(), "");
			TwAddVarRW(graphics_bar, "Vacancy No.", TW_TYPE_INT32, &packing_context()->set_vacant_group_no(), "");

			TwBar* function_bar = TwNewBar("Functions");
			TwDefine("Functions position='16 200' size='200 420' alpha=200");
									
			TwAddVarRW(function_bar, "Opti Algo", TW_TYPE_INT32, &Config_parameters::opti_algo, "group='Tools' min=1 max=2");
			//TwAddVarRW(function_bar, "Iter Number.", TW_TYPE_INT32, &spm()->setPackIterLim(), "min=1");
			TwAddButton(function_bar, "Pack", tw_pack, NULL, "group='Tools' key=p");
			//TwAddButton(function_bar, "Del Tri", tw_del_tri, NULL, "group='Tools' key=d");
			TwAddVarRW(function_bar, "Front Edge", TW_TYPE_DOUBLE, packing_context()->set_front_edge_sz(), "group='Tools' min=0.01 max=10.0");
			TwAddVarRW(function_bar, "Hole Area", TW_TYPE_DOUBLE, packing_context()->set_hole_face_sz(), "group='Tools' min=0.01 max=10.0");
			TwAddButton(function_bar, "Detect Hole", tw_det_holes, NULL, "group='Tools' key=h");
			TwAddButton(function_bar, "Fill Hole", tw_fill_holes, NULL, "group='Tools' key=f");
			TwAddVarRW(function_bar, "Gravity weight", TW_TYPE_DOUBLE, &Config_parameters::gravity_scale, "group='Tools' min=0.0001 max=50");
			TwAddButton(function_bar, "Retrive Vacancy", tw_retrieve_vacancy, NULL, "group='Tools' key=t");
			TwAddButton(function_bar, "Swap", tw_permute, NULL, "group='Tools' key=s");
			TwAddButton(function_bar, "Move State", tw_transfer_state, NULL, "group='Tools' key=m");
			TwAddButton(function_bar, "Retriangulate", tw_retriangulate, NULL, "group='Tools' key=r");
			TwAddButton(function_bar, "Update Tri", tw_update_triangulation, NULL, "group='Tools' key=u");
			//TwAddButton(function_bar, "SA", tw_sa, NULL, "group='Tools' key=R");
			TwAddButton(function_bar, "Multistart", tw_multistart, NULL, "group='Tools' key=M");
			TwAddVarRW(function_bar, "Nb trials", TW_TYPE_INT32, &packing_context()->max_trials(), "group='Tools' min=1 max=1000");
			TwAddVarRW(function_bar, "Upward gravity", TW_TYPE_BOOL8, &packing_context()->toggle_upward_gravity(), "");
			//TwAddButton(function_bar,"Fluctuate", tw_fluctuate, NULL, "group='Tools' key=f");
			TwAddButton(function_bar, "Print PDF", tw_print_pdf, NULL, "group='Tools' key=P");

			TwAddVarRW(function_bar, "dx", TW_TYPE_DOUBLE, &dx, "group='Debug'");
			TwAddVarRW(function_bar, "dy", TW_TYPE_DOUBLE, &dy, "group='Debug'");
			//TwAddButton(function_bar, "Move", tw_move, NULL, "group='Debug' key=m");
			TwAddButton(function_bar, "Energy", tw_get_energy, NULL, "group='Debug'");
			//TwAddButton(function_bar, "Plot", tw_plot, NULL, "group='Debug'");
			TwAddVarRW(function_bar, "Inter Res", TW_TYPE_BOOL8, &packing_context()->toggle_inter_result(), "group='Debug'");
			TwAddVarRW(function_bar, "Rm Scale", TW_TYPE_DOUBLE, &packing_context()->rm_scale_val(), "group='Debug' min=1 max=1000 step=1");
			TwAddVarRW(function_bar, "Rot Allowed", TW_TYPE_BOOL8, &packing_context()->toggle_rot(), "group=Debug'");
			TwAddButton(function_bar, "Clamp", tw_lower_container, NULL, "group='Debug'");
			//TwAddButton(function_bar, "Scale epsilon", tw_scale_rm, NULL, "group='Debug'");
			//TwAddButton(function_bar, "Save PNG", tw_save_png, NULL, "group='Debug'");

			toggle_skybox_CB() ;
            glut_viewer_add_toggle('b', glut_viewer_is_enabled_ptr(GLUT_VIEWER_BACKGROUND), "switch Color/BW") ;
            glut_viewer_add_toggle('v', &viewer_properties_->visible(), "viewer properties") ;

			*glut_viewer_is_enabled_ptr(GLUT_VIEWER_BACKGROUND) = GL_FALSE;
			viewer_properties_->visible() = GL_FALSE;

        }

    private:
        std::string polygon_filename ;

		// debug
		std::string debug_filename;
		double dx;
		double dy;
    } ;


Field_packing::Field_packing_application* packing_app() { return static_cast<Field_packing::Field_packing_application*>(Geex::GeexApp::instance()) ; }

void TW_CALL tw_pack(void *clientData) { packing_app()->pack(); }
//void TW_CALL tw_move(void *clientData) { packing_app()->move(); }
void TW_CALL tw_get_energy(void *clientData)	{ packing_app()->get_energy(); }
void TW_CALL tw_plot(void *clientData)	{ packing_app()->plot(); }
void TW_CALL tw_save_png(void *clientData)	{ packing_app()->save_png(); }
//void TW_CALL tw_fluctuate(void *clientData) { packing_app()->fluctuate(); }
void TW_CALL tw_lower_container(void *clientData) { packing_app()->lower_container(); }
//void TW_CALL tw_del_tri(void *clientData) { packing_app()->gen_delaunay(); }
void TW_CALL tw_det_holes(void *clientData){ packing_app()->detect_holes(); }
void TW_CALL tw_fill_holes(void *clientData) { packing_app()->fill_holes(); }
void TW_CALL tw_retrieve_vacancy(void* clientData) { packing_app()->retrieve_vacancy(); }
void TW_CALL tw_permute(void *clientData) { packing_app()->permute(); }
void TW_CALL tw_transfer_state(void* clientData) { packing_app()->transfer_state(); }
void TW_CALL tw_retriangulate(void* clientData) { packing_app()->retriangulate(); }
void TW_CALL tw_update_triangulation(void *clientData) { packing_app()->update_triangulation(); }
void TW_CALL tw_sa(void *clientData) { packing_app()->run_sa(); }
void TW_CALL tw_multistart(void *clientData) { packing_app()->multistart(); }
void TW_CALL tw_print_pdf(void *clientData)
{
	std::cout << "Print to file MyFile.pdf ...";
	FILE *fp = fopen("MyFile.pdf", "wb");
	GLint buffsize = 0, state = GL2PS_OVERFLOW;
	GLint viewport[4];

	glGetIntegerv(GL_VIEWPORT, viewport);

	while( state == GL2PS_OVERFLOW ){ 
		buffsize += 1024*1024;//GL2PS_BSP_SORT 
		gl2psBeginPage ( "MyTitle", "MySoftware", viewport,
			GL2PS_PDF, GL2PS_BSP_SORT, GL2PS_SILENT |
			GL2PS_SIMPLE_LINE_OFFSET | GL2PS_NO_BLENDING |
			GL2PS_OCCLUSION_CULL | GL2PS_BEST_ROOT | 
			GL2PS_NO_PS3_SHADING | GL2PS_COMPRESS,
			GL_RGBA, 0, NULL, 2, 2, 2, buffsize,
			fp, "MyFile" );
		glut_viewer_redraw(); 
		state = gl2psEndPage();
	}

	fclose(fp);
	std::cout <<" End!" <<std::endl;
}
//void TW_CALL tw_feature_set_callback(const void* fc, void *clientData) 
//{ 
//	spm_app()->feature_set_callback(*(double*)fc); 
//}
//void TW_CALL tw_feature_get_callback(void *value, void *clientData) 
//{ 
//	spm_app()->feature_get_callback((double*)value); 
//}
//void TW_CALL tw_scale_get_callback(void *value, void *clientData)
//{
//	packing_app()->rm_scale_get_callback((double*)value);
//}
//void TW_CALL tw_scale_set_callback(const void *value, void *clientData)
//{
//	packing_app()->rm_scale_set_callback(*(double*)value);
//}
}

int main(int argc, char** argv) 
{
	try
	{
		Geex::initialize();
		Field_packing::Field_packing_application app(argc, argv) ;
		app.main_loop() ;
		Geex::terminate() ;
	}
	catch (std::exception& e)
	{
		std::cout<<"Exception thrown: "<<e.what()<<std::endl;
	}
	catch (...)
	{
		std::cout<<"Unknown exception.\n";
	}
	return 0;
}
